package eval.host;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.inject.Singleton;
import eval.utilities.configuration.ExperimentConfiguration;
import eval.vm.VirtualMachine;
import eval.deploy.IDemandManager;
//import eval.host.compare.ISortHostList;
import eval.exceptions.CpuLeakAtHostException;
import eval.exceptions.MemLeakAtHostException;
import eval.exceptions.VmNotFoundException;

/**
 * It manages the load of hosts from configuration files, their distribution and
 * the generation of new hosts according to the distribution
 * 
 * @author Nicolo' M. Calcavecchia <calcavecchia@elet.polimi.it>
 * */
@Singleton
public class HostManager implements IHostManager {


	private static ExperimentConfiguration configuration = ExperimentConfiguration.getSingleton();
	
	
	/**
	 * Map holding the various host capacities
	 * */
	private Map<HostCapacityEnum, HostCapacity> hostCapacities = new HashMap<HostCapacityEnum, HostCapacity>();
	private Map<HostCapacityEnum, Double> hostDistribution = new HashMap<HostCapacityEnum, Double>();
	private Map<HostCapacityEnum, Integer> hostStartUpCostUnit = new HashMap<HostCapacityEnum, Integer>();
	private Map<HostCapacityEnum, Integer> hostBAseUpCostUnit = new HashMap<HostCapacityEnum, Integer>();
	/**
	 * Data structure containing the deployed hosts
	 * */

	private List<Host> hosts = new ArrayList<Host>();
	
	private double totalCpuCapacity = 0;
	private double totalMemCapacity = 0;

	public HostManager() {
		loadHostProperties();
		//TODO  
		//openNewHost();
	}

	@Override
	public int getNumberOfHosts() {
		return hosts.size();
	}
	
	@Override
	public int getNumberOfOpenHosts() {
		int count = 0;
		for(Host host : hosts){
			if(host.isOpen()){
				count++;
			}
		}
		return count;	
	}
	
	@Override
	public int getNumberOfClosedHosts() {
		int count = 0;
		for(Host host : hosts){
			if(host.isClose()){
				count++;
			}
		}
		return count;	
	}
	
	@Override
	public int getNumberOfCandidatesForClosingHosts() {
		int count = 0;
		for(Host host : hosts){
			if(host.isCandidateForClosing()){
				count++;
			}
		}
		return count;
	}
	
	@Override
	public Host getHostByID(int hostIdentifier) {
		for (Host host : hosts){
			if(host.getHostId() == hostIdentifier)
				return host;
		}
		return null;
	}

	@Override
	public List<Host> getHosts() {
		return hosts;
	}
	
	
	@Override
	public List<Host> getOpenHosts() {
		List<Host> openHosts =  new ArrayList<Host>();
		for(Host host : hosts){
			if(host.isOpen() /* || host.isCandidateForClosing() */){
				openHosts.add(host);
			}
		}
		return openHosts;
	}

	private void loadHostProperties() {
		double probabilitySum = 0;

		for (HostCapacityEnum value : HostCapacityEnum.values()) {
			hostCapacities.put(value, createNewHostCapacity(value));
			double probability = getHostDistribution(value);
			hostDistribution.put(value, probability);
			probabilitySum += probability;
			hostStartUpCostUnit.put(value, configuration.getStartUpUnitCost(value));
			hostBAseUpCostUnit.put(value,configuration.getBAseUnitCost(value));
		}

		if (probabilitySum != 1)
			throw new IllegalArgumentException(
					"The sum of probabilities for host distribution is not 1!");

	}

	private double getHostDistribution(HostCapacityEnum value) {
		switch (value) {
		case SMALL:
			return 1.0;
		default:
			throw new IllegalArgumentException("Invalid value for "
					+ HostCapacityEnum.class.getSimpleName() + " enumeration");

		}
	}

	public HostCapacity createNewHostCapacity(HostCapacityEnum hostCapacityEnum) {

		int cpuCapacity = 0;
		int memCapacity = 0;
		int powerValue = 0;
		int hostBaseEnergyConsumption = 0;
		int hostPowerOnEnergyConsumption = 0;

		switch (hostCapacityEnum) {
		case SMALL:
			cpuCapacity = configuration.smallHostCpuCapacity;
			memCapacity = configuration.smallHostMemCapacity;
			powerValue =  configuration.smallHostPowerValue;
			hostBaseEnergyConsumption = configuration.smallhostBaseEnergyConsumption;
			hostPowerOnEnergyConsumption = configuration.smallhostPowerOnConsumption;
			break;
		default:
			throw new IllegalArgumentException("Invalid value for "
					+ HostCapacityEnum.class.getSimpleName() + " enumeration");
		}

		if (cpuCapacity == 0 || memCapacity == 0)
			throw new IllegalArgumentException(
					"Invalid values for host capacity, cpu: " + cpuCapacity
							+ ", mem: " + memCapacity);

		return new HostCapacity(cpuCapacity, memCapacity,powerValue, hostBaseEnergyConsumption,hostPowerOnEnergyConsumption);

	}

	@Override
	public int getCpuCapacityForHost(Host host) {
		return (int) hostCapacities.get(host.getHostCapacityEnum())
				.getCpuCapacity();
	}

	@Override
	public int getMemCapacityForHost(Host host) {
		return (int) hostCapacities.get(host.getHostCapacityEnum())
				.getMemCapacity();
	}

	@Override
	public void printAllHosts() {
		for (HostCapacityEnum hostCapacity : HostCapacityEnum.values()) {

			System.out.print("HOST " + hostCapacity.name() + " ["
					+ hostCapacities.get(hostCapacity) + "]");

			List<Integer> hostFound = new ArrayList<Integer>();
			for (int i = 0; i < hosts.size(); i++) {
				if (hosts.get(i).equals(hostCapacity))
					hostFound.add(i);
			}

			System.out.println(" ->  " + hostFound);
		}
	}

	@Override
	public int getTotalCpuCapacity() {
		return (int) totalCpuCapacity;
	}

	@Override
	public int getTotalMemCapacity() {
		return (int) totalMemCapacity;
	}
	
	@Override
	public void addHosts(List<Host> hosts) {
		for (Host host : hosts) {
			addHost(host);
		}	
	}

	@Override
	public void addHost(Host host) {
		hosts.add(host);
		totalCpuCapacity += hostCapacities.get(host.getHostCapacityEnum())
				.getCpuCapacity();
		totalMemCapacity += hostCapacities.get(host.getHostCapacityEnum())
				.getMemCapacity();
		host.close();
	}

	@Override
	public Map<HostCapacityEnum, Double> getHostDistribution() {
		return new HashMap<HostCapacityEnum, Double>(hostDistribution);
	}

	@Override
	public int getCpuCapacityOfType(HostCapacityEnum hostType) {
		return (int) hostCapacities.get(hostType).getCpuCapacity();
	}

	@Override
	public int getMemCapacityOfType(HostCapacityEnum hostType) {
		return (int) hostCapacities.get(hostType).getMemCapacity();
	}

	

	@Override
	public HostCapacity getHostCapacity(HostCapacityEnum hostType) {
		return hostCapacities.get(hostType);
	}

	

	@Override
	public void closeHost(Host host) {
		host.close();
		//TODO
		//chg because Cplex
//		if(host.isCandidateForClosing()){
//			host.close();
//		}
//		else{
//			System.out.println("error- we tried to close an host (hostID: " + host.getHostId() + ")which is not candidate for closeing.");
//		}
//		
	}
	
	@Override
	public void updateCurrentHostDemand(IDemandManager demandManager,Host host , List<VirtualMachine> vmsInHost, int tick){
		int Demand = 0;
		for (VirtualMachine vms : vmsInHost) {
			Demand += demandManager.getRealDemand(vms.getIdentifier()).getCPUDemandAtTick(tick);
		}
		//System.out.print(" host: " + host.getHostId() + " demand: " +Demand + " ");
		host.setCurrentCpuDemand(Demand);
	}
	
	
	
	@Override
	public boolean openNewHostWithMinCpu(double minCpuNeeded,int tick) {
		if(configuration.totalNumberOfHosts <= getNumberOfOpenHosts())
			return false;
		HostCapacityEnum typeofHost = null;
		for(Map.Entry<HostCapacityEnum, HostCapacity> entry : hostCapacities.entrySet()) {
			if(entry.getValue().getCpuCapacity()>= minCpuNeeded) {
				typeofHost = entry.getKey();
			}
		}
		if(typeofHost != null) {
			for(Host host: hosts) {
				if(host.isClose() && host.getHostCapacityEnum()==typeofHost) {
					host.open(tick);
					return true;
				}
			}
		}
		double cpuSatisfied = 0;
		boolean flag = false;
		Host host;
		do {
			host = openNewHost(tick);
			if(host != null) {
				cpuSatisfied += getCpuCapacityForHost(host); 
				flag = true;
			} else
				return flag;
		}while(cpuSatisfied<minCpuNeeded);
		return flag;
	}
	
	@Override
	public Host openNewHost(int tick) {
		if(configuration.totalNumberOfHosts <= getNumberOfOpenHosts()){
			return null;
		}
		
		Host host = findBestHostToOpen();
		if(host!=null && host.isClose()){
			host.open(tick);
			//TODO:update also hosts list.
			//TODO delete next line?
		//	getHostByID(host.getHostId()).open(tick);
			return host;
		}
		else if(host == null)
		{System.out.println("ERROR(#1): cant open new Host.");}
		return null;
	}
	
	@Override
	public void openHost(Host host,int tick) {
		host.open(tick);
	}
	
	public Host findBestHostToOpen(){
		Host bestHost = null;
		int maxCapcity = 0;
		int CurhostCpu;
		for(Host host : hosts){
			CurhostCpu = host.getHostCapacity().getCpuCapacity();
			if(host.isClose() && CurhostCpu > maxCapcity){
				maxCapcity = CurhostCpu;
				bestHost = host;
			}
		}
		return bestHost;
		
		//TODO:
		//Consider to perform different sort.
	}
	
	public void InitHostsInCloud(){
		Host host = openNewHost(0);
		for (Host curHost : hosts) {
			if(host.getHostId() != curHost.getHostId()){
				curHost.close();
			}
		}	
	
	}

	@Override
	public int getToatalCpuLoadOnCloud() {
		int totalLoad = 0;
		for(Host host : hosts) {
			if(host.isOpen())
				totalLoad += host.getCurrentCpuLoad();
		}
		return totalLoad;
	}

	

	@Override
	public void remAllCandidateForClosing() {
		for(Host host : hosts) {
			if(host.isCandidateForClosing())
				removeHostFromCandidateForClosing(host);
		}
	}

	@Override
	public void closeHostsWithCpuSumOf(int sumOfCpu) {
		for(Host host : hosts) {
			if(host.isOpen() && host.getCurrentCpuLoad()==0 &&
					getCpuCapacityForHost(host)<=sumOfCpu) {
				host.close();
				sumOfCpu -= getCpuCapacityForHost(host);
				if(sumOfCpu==0)
					return;
			}
		}
	}

	@Override
	public void makeHostCandidateForClosing(Host host,int tickRemainingForClosing) {
		host.makeCandidateForClosing(tickRemainingForClosing);
	}

	@Override
	public void removeHostFromCandidateForClosing(Host host) {
		host.remCandidateForClosing();
	}

	@Override
	public void setHostCurrentLoad(Host host, int cpuLoad, int memLoad) {
		try {
			setHostCurrentCpuLoad(host,cpuLoad);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			setHostCurrentMemLoad(host,memLoad);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	@Override
	public void setHostCurrentCpuLoad(Host host, int cpuLoad) throws Exception {
		if(host.getHostCapacity().getCpuCapacity()<cpuLoad)
			throw new Exception("setHostCurrentCpuLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+cpuLoad+" Host caps: "+host.getHostCapacity().getCpuCapacity());
		host.setCurrentCpuLoad(cpuLoad);
	}

	@Override
	public void setHostCurrentMemLoad(Host host, int memLoad) throws Exception {
		if(host.getHostCapacity().getMemCapacity()<memLoad)
			throw new Exception("setHostCurrentMemLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+memLoad+" Host caps: "+host.getHostCapacity().getMemCapacity());
		host.setCurrentMemLoad(memLoad);		
	}

	@Override
	public void updateHostCurrentLoad(Host host, int cpuLoad, int memLoad) {
		try {
			updateHostCurrentCpuLoad(host,cpuLoad);
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		try {
			updateHostCurrentMemLoad(host,memLoad);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void updateHostCurrentCpuLoad(Host host, int cpuLoad) throws CpuLeakAtHostException {
		if(host.getHostCapacity().getCpuCapacity()<cpuLoad+host.getCurrentCpuLoad())
			throw new CpuLeakAtHostException("updateHostCurrentCpuLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Cpu req: "+cpuLoad+" after update: "+ cpuLoad+host.getCurrentCpuLoad() +" Host Cpu caps: "+host.getHostCapacity().getMemCapacity());
		host.updateCurrentCpuLoad(cpuLoad);
	}

	@Override
	public void updateHostCurrentMemLoad(Host host, int memLoad) throws MemLeakAtHostException {
		if(host.getHostCapacity().getCpuCapacity()<memLoad+host.getCurrentMemLoad())
			throw new MemLeakAtHostException("updateHostCurrentMemLoad: overLoad on Host. HostId:"+host.getHostId()+
					" Mem req: "+memLoad+" after update: "+ memLoad+host.getCurrentMemLoad() +"Host Mem caps: "+host.getHostCapacity().getMemCapacity());
		host.updateCurrentMemLoad(memLoad);
	}

	@Override
	public void setHostCurrentCpuDemand(Host host, int cpuDemand) {
		host.setCurrentCpuDemand(cpuDemand);
	}

	@Override
	public void removeListOfVmFromHosts(List<VirtualMachine> removeList) {
		for(Host host : hosts) {
			host.removeListOfVmFromHost(removeList);
		}
	}
	
//	@Override
//	public void addVmToHost(Host host, VirtualMachine vm) {
//		host.addVmToHost(vm);
//	}
	
	@Override
	public void addVmToHost(Host host, VirtualMachine vm, int cpuLoad, int memLoad) {
		host.addVmToHost(vm, cpuLoad, memLoad);
	}

	@Override
	public List<VirtualMachine> getVmsAtHost(Host host) {
		return host.getVmsAtHost();
	}

	@Override
	public int getCpuAvailableAtHost(Host host) {
		return host.getHostCapacity().getCpuCapacity() - host.getCurrentCpuLoad();
	}

	@Override
	public int getTickHostTurnOn(Host host) {
		return host.getTickHostTurnOn();
	}

	@Override
	public double getStartUpCostForHost(HostCapacityEnum hostCapacityEnum) {
		return hostStartUpCostUnit.get(hostCapacityEnum);
	}

	@Override
	public double getLoadCost(int load) {
		return load*configuration.loadCost;
	}

	@Override
	public double getIncomeOnLoad(int totalCloudCPULoad) {
		return totalCloudCPULoad*configuration.incomeOnLoad;
	}

	@Override
	public int getToatalMemLoadOnCloud() {
		int totalMem = 0;
		for(Host host : getOpenHosts()) {
			totalMem += host.getCurrentMemLoad();
		}
		return totalMem;
	}

	@Override
	public int getCPUReservation(Host host) {
		int sum = 0;
		for(VirtualMachine vm : getVmsAtHost(host)){
			sum += vm.getVMConfiguration().getMinCpu();
		}
		return sum;
	}

	@Override
	public double getBaseCostForHost(HostCapacityEnum hostCapacityEnum) {
		return hostBAseUpCostUnit.get(hostCapacityEnum);
	}

	@Override
	public Host getVm(VirtualMachine vm) {
		for(Host host : hosts) {
			if(host.isOpen() && host.getVmsAtHost().contains(vm)) 
				return host;
		}
		throw new VmNotFoundException("ERROR vm: "+vm.getIdentifier().getID()+
				" vm not at found");
	}

	@Override
	public void removeVmFromHost(Host host, VirtualMachine vm) {
		host.removeVmFromHost(vm);
	}

	@Override
	public void relocateVm(VirtualMachine vm, Host fromHost, Host toHost, int cpu, int mem) {
		fromHost.removeVmFromHost(vm);
		toHost.addVmToHost(vm,cpu,mem);
		
	}

	@Override
	public List<VirtualMachine> getAllVmAtOpenHosts() {
		List<VirtualMachine> toRet = new ArrayList<VirtualMachine>();
		for(Host host :getOpenHosts()) 
			toRet.addAll(host.getVmsAtHost());
		return toRet;
	}

	@Override
	public void rejectVms(VirtualMachine vm) {
		for(Host host : hosts) {
			if(host.isOpen()) {
				host.removeVmFromHost(vm);
			}
		}
		
	}



}
